/**
 * Copyright 2014 opencxa.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.cxa.rpc.cloud.server;


import java.util.List;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.cxa.exec.updater.Updatable;
import org.cxa.rpc.cloud.server.components.DeviceComponent;
import org.cxa.rpc.cloud.server.components.authentication.Group;
import org.cxa.rpc.cloud.server.components.authentication.User;
import org.cxa.rpc.messages.RequestRpcMessage;
import org.cxa.rpc.messages.ResponseRpcMessage;
import org.cxa.rpc.messages.RpcMessage;
import org.cxa.rpc.messages.RpcMessage.MessageType;
import org.cxa.rpc.session.SessionManager;
import org.cxa.rpc.session.SessionManager.SessionListenerAdapter;
import org.cxa.rpc.transportChannel.TransportChannel;
import org.cxa.rpc.transportChannel.TransportChannel.DisconnectReason;
import org.cxa.rpc.transportChannel.TransportChannel.TransportChannelListenerAdapter;


/**
 * @author Christopher Armenio
 */
public class DeviceProxy implements Updatable
{
	private static final String META_FIELD_SOURCE_DP = "sourceDp";
	
	private final Logger logger = LogManager.getLogger(this.toString());
	private final SessionManager sessionManager;
	private Boolean isUpdating = false;
	
	private User owner = null;
	private List<Group> groups = null;
	private String serialNumber = null;
	
	
	public DeviceProxy(TransportChannel transportChannelIn)
	{	
		// setup our session manager
		this.sessionManager = new SessionManager(transportChannelIn);
		this.logger.debug("owns " + this.sessionManager.toString());
		this.sessionManager.addSessionListener(new SessionListenerAdapter()
		{
			@Override
			public void onSessionClose(DisconnectReason reasonIn)
			{
				IoBroker.getSingleton().removeDisconnectedDeviceProxy(DeviceProxy.this);
			}
		});
		this.sessionManager.start();
		
		// we need to register a transport channel listener so we can
		// receive request, response, notifications
		// (session manager only allows requests, notifications)
		this.sessionManager.getTransportChannel().addTransportChannelListener(new TransportChannelListenerAdapter()
		{
			public void onMessageReceived(RpcMessage msgIn)
			{
				// we only handle the higher-level message types...
				if( (msgIn.getMsgType() != MessageType.REQUEST) && (msgIn.getMsgType() != MessageType.RESPONSE) && (msgIn.getMsgType() != MessageType.NOTIFICATION) ) return;
				
				msgIn.setMetaData(META_FIELD_SOURCE_DP, DeviceProxy.this);
				msgIn.setSource(String.format("/%s/%s", DeviceComponent.PATH_PREFIX, DeviceProxy.this.getSerialNumber()));
				IoBroker.getSingleton().routeMessage(msgIn);
			}
		});
	}
	
	
	public void setOwner(User ownerIn)
	{
		this.owner = ownerIn;
	}
	
	
	public User getOwner()
	{
		return this.owner;
	}
	
	
	public void setGroups(List<Group> groupsIn)
	{
		this.groups = groupsIn;
	}
	
	
	public List<Group> getGroups()
	{
		return this.groups;
	}
	
	
	public String getSerialNumber()
	{
		return (this.serialNumber == null) ? String.format("td-%X", System.identityHashCode(this)) : this.serialNumber;
	}
	
	
	public void forwardMessage(RpcMessage msgIn)
	{
		// we're being asked to forward a message to our connected device...let's do some security checks...
		DeviceProxy sourceDp = DeviceProxy.extractSourceDeviceProxy_fromMessageMetaData(msgIn);
		
		// now, check to see if we're in the same group as the source DP
		// null sourceDp means it's from a system component
		boolean sameGroup = (sourceDp == null);
		if( (sourceDp != null) && (this.groups != null) && (sourceDp.groups != null) )
		{
			for( Group currGroup_ours : this.groups )
			{
				for( Group currGroup_source : sourceDp.groups )
				{
					if( currGroup_ours.getId() == currGroup_source.getId() )
					{
						sameGroup = true;
						break;
					}
				}
			}
		}
		
		// now check to see if we have the same owner as the source DP
		boolean sameOwner = ((sourceDp == null) || (this.owner == null) || (sourceDp.owner == null)) ? false : (this.owner.getId() == sourceDp.owner.getId());
		
		// set the permissions on the message
		msgIn.setPermissions(sameGroup, sameOwner);
		
		// we need to strip the leading info from the destination for requests and responses
		if( (msgIn.getMsgType() == MessageType.REQUEST) || (msgIn.getMsgType() == MessageType.RESPONSE) )
		{
			String pathComponents[] = RpcMessage.parsePathComponents(
					(msgIn.getMsgType() == MessageType.REQUEST) ? 
					(((RequestRpcMessage)msgIn).getDestination()) :
					(((ResponseRpcMessage)msgIn).getDestination()) );
			
			
			// first, we need to find our serial number
			int indexOfSerialNum = 0;
			for( indexOfSerialNum = 0; indexOfSerialNum < pathComponents.length; indexOfSerialNum++ )
			{
				if( pathComponents[indexOfSerialNum].equals((this.serialNumber != null) ? this.serialNumber : String.format("%X", System.identityHashCode(this))) ) break;
			}
			
			String newDest = "/";
			for( int i = indexOfSerialNum+1; i < pathComponents.length; i++ )
			{
				newDest += pathComponents[i];
				if( i != (pathComponents.length-1) ) newDest += "/";
			}
			
			if( msgIn.getMsgType() == MessageType.REQUEST ) ((RequestRpcMessage)msgIn).setDestination(newDest);
			else ((ResponseRpcMessage)msgIn).setDestination(newDest);
		}
		
		this.sessionManager.sendMessage(msgIn);
	}
	
	
	protected Boolean isUpdating()
	{
		return this.isUpdating;
	}


	@Override
	public boolean start()
	{
		return this.sessionManager.start();
	}


	@Override
	public void update()
	{
		this.isUpdating = true;
		this.sessionManager.update();
		this.isUpdating = false;
	}


	@Override
	public void stop()
	{
		this.sessionManager.stop();
	}
	
	
	/*
	 * (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString()
	{
		return String.format("%s.%s", this.getClass().getSimpleName(), System.identityHashCode(this));
	}
	
	
	public static DeviceProxy extractSourceDeviceProxy_fromMessageMetaData(RpcMessage msgIn)
	{
		DeviceProxy retVal = null;
		Object destDp_raw = msgIn.getMetaData_byFieldName(DeviceProxy.META_FIELD_SOURCE_DP);
		if( (destDp_raw != null) && (destDp_raw instanceof DeviceProxy) )
		{
			retVal = (DeviceProxy)destDp_raw;
		}
		return  retVal;
	}
}
